Shell学习笔记04-Shell变量知识进阶

GO


1. Shell中特殊且重要的变量

1.1. Shell中的特殊位置参数变量

在Shell中存在一些特殊且重要的变量,例如:$0$1$#等,我们称之为特殊位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在Shell脚本中使用位置参数变量。下表为常用的特殊位置参数变量的说明:

位置变量 作用说明
$0 获取当前执行的Shell脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径
$n 获取当前执行的Shell脚本的第n个参数,n=1..9,当n为0时表示脚本的文件名;如果n大于9,则要用大括号括起来,例如${10},接的参数以空格隔开
$# 获取当前执行的Shell脚本后面接的参数的总个数
$* 获取当前Shell脚本所有传参的参数,不加引号和$@相同;如果给$*加上双引号,例如:"$*",则表示将所有的参数视为单个字符串,相当于$1 $2 $3
$@ 获取当前Shell脚本所有传参的参数,不加引号和$*相同;如果给它加上双引号,例如:$@,则表示将所有的参数视为不同的独立字符串,相当于"$1" "$2" "$3" "..."。这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。当$@$*都加双引号时,两者是有区别的;都不加双引号时,两者无区别。

1.1.1. $n的用法总结

  • 加引号括起来的内容传参,会作为一个字符串参数,如:sh test.sh "theshu boy"theshu boy作为一个参数
  • $n的n大于9时,必须要这样用才能得到正确的结果:${n},示例代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [theshu@theshu ~]$ echo \${1..15} #<==利用大括号输出15个位置参数,学会了
    该命令就不用手敲代码了。
    $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15
    [theshu@theshu ~]$ echo \${1..15} > n.sh #<==将15个位置参数定向到文件n.sh
    [theshu@theshu ~]$ vim n.sh #<==在代码的最前面添加echo
    [theshu@theshu ~]$ cat n.sh #<==最终的测试代码
    echo $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15
    [theshu@theshu ~]$ echo {a..z} #<=测试打印26个英文字母并以空格分隔
    a b c d e f g h i j k l m n o p q r s t u v w x y z
    [theshu@theshu ~]$ sh n.sh {a..z} #<==传入26个英文字母,以空格分隔作为参数
    a b c d e f g h i a0 a1 a2 a3 a4 a5 #<==位置参数大于9后,输出的内容就不对了
    [theshu@theshu ~]$ vim n.sh #<==修改代码
    [theshu@theshu ~]$ cat n.sh #<==最终代码如下
    echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11} ${12} ${13} ${14} ${15}
    [theshu@theshu ~]$ sh n.sh {a..z} #重新测试
    a b c d e f g h i j k l m n o #<==位置参数大于9的也正确了

1.1.2. $0特殊变量的作用及变量实践

  • $0的作用为取出执行脚本的名称(包括路径)。若不带路径执行脚本,那么输出结果就是脚本的名字,若使用绝对路径执行脚本,那么输出结果就是全路径加上脚本的名字。示例代码如下:

    1
    2
    3
    4
    5
    6
    [theshu@theshu ~]$ cat n.sh
    echo $0
    [theshu@theshu ~]$ sh n.sh
    n.sh
    [theshu@theshu ~]$ sh /home/theshu/n.sh
    /home/theshu/n.sh
  • 当执行的脚本为绝对路径时,$0也会带着路径,这时如果希望单独获取名称或路径,可以利用dirnamebasename这两个命令。示例代买如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [theshu@theshu ~]$ dirname /home/theshu/n.sh
    /home/theshu #<==dirname命令的作用是获取脚本的路径
    [theshu@theshu ~]$ basename /home/theshu/n.sh
    n.sh #<==basename命令的作用是获取脚本的名字
    #<==利用$0和dirname以及basename命令分别取出脚本名称和脚本路径的代码:
    [theshu@theshu ~]$ cat n.sh
    dirname $0
    basename $0
    [theshu@theshu ~]$ sh /home/theshu/n.sh
    /home/theshu
    n.sh
  • 有关$0这个位置参数的系统生产场景案例如下,其中采用aegis系统脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [theshu@theshu ~]$ tail -6 /etc/init.d/aegis
    echo $"Usage: $0 {start|stop|restart|status|uninstall}"
    #<==$0的基本生产场景就是,当用户的输入不符合脚本的要求时,就打印脚本的名字及使用帮助。
    exit 1
    ;;
    esac
    [theshu@theshu ~]$ sudo /etc/init.d/aegis #<==不带任何参数执行aegis脚本
    Usage: /etc/init.d/aegis {start|stop|restart|status|uninstall}
    #<==上文/etc/init.d/aegis就是$0从脚本命令行获取的值,当用户输入不符合脚本设定的要求时,打印脚本名字及预期的使用帮助

1.1.3. $#特殊变量获取脚本传参个数的实践

例子:通过$#获取脚本传参的个数。

1
2
3
4
5
6
[theshu@theshu ~]$ cat q.sh
echo $1 $2 $3 $4 $5 $6 $7 $8 $9
echo $# #<==此行是打印脚本命令行传参的个数
[theshu@theshu ~]$ sh q.sh {a..z} #<==传入26个字符作为26个参数
a b c d e f g h i #<==只接收了9个变量,所以打印9个字符
26 #<==传入26个字符作为26个参数,因此这里的数字为26,说明传入了26个参数

下面是一个针对$0$1$#等多位置参数的综合型企业案例,它的作用是根据用户在命令行的传参个数判断用户的输入,不合要求的给予提示并退出。
首先来看条件表达式判断语句的写法,如下:

1
2
3
4
5
6
7
8
9
10
[theshu@theshu ~]$ cat t1.sh
[ $# -ne 2 ] && {
echo "Must be two args"
exit 1
}
echo ok
[theshu@theshu ~]$ sh t1.sh
Must be two args
[theshu@theshu ~]$ sh t1.sh arg1 arg2
ok

然后是if判断语句的写法,如下:

1
2
3
4
5
6
7
8
9
10
11
[theshu@theshu ~]$ cat t2.sh
if [ $# -ne 2 ]
then
echo "USAGE:/bin/sh $0 arg1 arg2"
exit 1
fi
echo $1 $2
[theshu@theshu ~]$ sh t2.sh
USAGE:/bin/sh t2.sh arg1 arg2
[theshu@theshu ~]$ sh t2.sh theshu boy
theshu boy

1.1.4. $*和$@特殊变量功能及区别说明

例子:利用set命令设置位置参数(同命令行脚本的传参)

1
2
3
4
5
6
7
8
9
10
[theshu@theshu ~]$ set -- "I am" good man.
#<==通过set设置了三个字符串参数,"--"表示清除所有的参数变量,重新设置后面的参数变量
[theshu@theshu ~]$ echo $# #<==输出参数的个数
3
[theshu@theshu ~]$ echo $1 #<==打印第一个参数值
I am
[theshu@theshu ~]$ echo $2 #<==打印第二个参数值
good
[theshu@theshu ~]$ echo $3 #<==打印打三个参数值
man.

测试$*$@,注意,此时不带双引号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[theshu@theshu ~]$ echo $* #<==打印$*
I am good man.
[theshu@theshu ~]$ echo $@ #<==打印$@
I am good man.
[theshu@theshu ~]$ for i in $*;do echo $i;done #<==使用for循环输出$*测试
I #<==($*)不加双引号,因此会输出所有参数,然后第一个参数"I am"也拆开输出了
am
good
man.
[theshu@theshu ~]$ for i in $@;do echo $i;done #<==使用for循环输出$@测试
I #<==($0)不加双引号,因此会输出所有参数,然后第一个参数"I am"也拆开输出了
am
good
man.

测试$*$@,注意,此时带有双引号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[theshu@theshu ~]$ echo "$*"
I am good man.
[theshu@theshu ~]$ echo "$@"
I am good man.
[theshu@theshu ~]$ for i in "$*";do echo $i;done
#<==在有双引号的情况下"$*",参数里引号中的内容当作一个参数输出了
I am good man.
[theshu@theshu ~]$ for i in "$@";do echo $i;done
#<==在有双引号的情况下,每个参数均以独立的内容输出
I am #<==有双引号算一个参数
good
man.
#<==这才真正符合我们传入的参数需求,set -- "I am" good man.
[theshu@theshu ~]$ for i;do echo $i;done
#<==去掉in变量列表,相当于有引号的in "$@"
I am
good
man.
#<==这才真正符合我们传入的参数需求,set -- "I am" good man.

可以利用shift将位置参数移位(左移):

1
2
3
4
5
6
7
[theshu@theshu ~]$ shift
[theshu@theshu ~]$ echo $# #<==移位(左移)
2
[theshu@theshu ~]$ echo $1 #<==这里就打印原来的$2的值了
good
[theshu@theshu ~]$ echo $2 #<==这里就打印原来的$3的值了
man.

1.2. Shell进程中的特殊状态变量

Shell进程中的特殊状态变量如下表:(提示:下表的查找方式为使用man bash命令,然后搜索关键字Special Parameteers

位置变量 作用说明
$? 获取执行上一个指令的执行装填返回值(0为成功,非零为失败),这个变量最常用
$$ 获取当前执行的Shell脚本的进程号(PID),这个变量不常用,了解即可
$! 获取上一个在后台工作的进程的进程号(PID),这个变量不常用,了解即可
$_ 湖区在此之前执行的命令或脚本的最后一个参数,这个变量不常用,了解即可

1.2.1. 关于$?的说明

  • 在不通命令的执行结果中,$?的返回值不尽相同,但在工作场景中,常用的就是0和非0两种状态,0表示成功运行,非0表示运行失败。
  • 若使用源码编译安装软件,可以在每个步骤的结尾获取$?来判断命令执行成功与否。
  • 对于新手来说,在安装服务时,可以通过获取执行命令的返回值来确定命令的执行状态,从而快速确定命令是否执行成功。不过,有经验的技术人员不需要获取返回值,通过命令的最后过程输出就可以快速判断是否成功。
  • 当对服务器的数据进行备份时,我们会在执行完关键命令,例如tarcp后,通过获取返回值来判断命令是否执行成功,备份数据是否完整。

在企业场景下,$?返回值的用法如下:

  1. 判断命令、脚本或函数等程序是否执行成功
  2. 若在脚本中调用执行exit 数字,则会返回这个数字给$?变量
  3. 如果是在函数里,则通过return 数字把这个数字以函数返回值的形式传给$?

1.2.2. 关于$$的应用

例子:实现系统中多次执行某一个脚本后的进程只有一个(此为$$的企业级应用)

说明:有时执行定时任务脚本的频率比较快,并不知道上一个脚本是否真的执行完毕,但是,业务要求同一时刻只能有一个同样的脚本在运行,此时就可以利用$$获取上一次运行的脚本进程号,当程序重新运行时,根据获得的进程号,清理掉上一次的进程,运行新的脚本命令。脚本如下:

1
2
3
4
5
6
7
8
9
#!/bin/sh
pidpath=/tmp/a.pid #<==定义pid文件
if [ -f "$pidpath" ] #<==如附哦pid存在,则执行then后面的命令
then
kill `cat $pidpath` > /dev/null 2>&1 #<==杀掉与前一个进程号对应的进程
rm -f $pidpath #<==删除pid文件
fi
echo $$ > $pidpath #<==将当前Shell进程号记录到pid文件里
sleep 300

提示:这是一个生产案例的简单模拟,脚本用于执行启动或定时任务时,相同的脚本中只能有一个在运行,当新脚本运行时,必须关闭未完成或未退出的上一次的同名脚本进程。

1.2.3. 关于$_的说明及实践

$_的作用是获得上一条命令的最后一个参数值,此功能用的不多,了解即可。示例如下:

1
2
3
4
5
6
7
8
[theshu@theshu ~]$ echo hello
hello
[theshu@theshu ~]$ echo $_
hello
[theshu@theshu ~]$ ll -d /root/
dr-xr-x---. 9 root root 4096 Feb 25 17:10 /root/
[theshu@theshu ~]$ echo $_
/root/

1.2.4. 关于$!的说明及实践

$!的功能类似与$$,只不过作用是获取上一次执行脚本的pid,对此,了解即可。范例如下:

1
2
3
4
5
[theshu@theshu ~]$ ps ef | grep pid.sh | grep -v grep
[theshu@theshu ~]$ sh pid.sh &
[1] 12077
[theshu@theshu ~]$ echo $!
12077

2. bash Shell内置变量命令

bash Shell包含一些内置命令,它们是由Shell本身提供的。常用的内部命令有:echo、eval、exec、export、read、shift。下面简单介绍几个最常用的内置命令的格式和功能。

2.1. echo在屏幕上输出信息

  • 命令格式:echo args (可以是字符串和变量的组成)
  • 功能说明:将echo命令后面args指定的字符串及变量等显示到标准输出
  • 常用参数见下表:
echo参数选项 说明
-n 不换行输出内容
-e 解析转义字符(见下面的字符)
转义字符
\n 换行
\r 回车
\t 制表符(tab)
\b 退格
\v 纵向制表符

参数的应用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[theshu@theshu ~]$ echo theshu;echo hello
theshu
hello
[theshu@theshu ~]$ echo -n theshu;echo hello #<==-n不换行输出
theshuhello
[theshu@theshu ~]$ echo "theshu\ttheshu\ntheshu\ttheshu"
theshu\ttheshu\ntheshu\ttheshu
[theshu@theshu ~]$ echo -e "theshu\ttheshu\ntheshu\ttheshu" #<==加上-e解析"\"开头的字符
theshu theshu
theshu theshu
[theshu@theshu ~]$ printf "theshu\ttheshu\ntheshu\ttheshu\n"
theshu theshu
theshu theshu
#<==printf的转义字符能力与echo类似
[theshu@theshu ~]$ echo -e "1\b23" #<==加上-e解析以"\"开头的字符,\b退格
23
[theshu@theshu ~]$ printf "1\b23\n" #<==printf的转义字符功能与echo类似
23

printfecho的功能类似,但是printf更强大,当需要特殊复杂的格式时才考虑使用printf

2.2. eval(后文有案例讲解)

  • 命令格式:eval args
  • 功能:当Shell程序执行到eval语句时,Shell读入参数args,并将它们组合成一个新的命令,然后执行。
  • 范例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    [theshu@theshu ~]$ cat noeval.sh
    echo \$$#
    [theshu@theshu ~]$ sh noeval.sh arg1 arg2
    $2
    [theshu@theshu ~]$ cat noeval.sh
    eval "echo \$$# "
    #<==加上eval命令,使得打印的也是位置参数,重新解析输出,而不是输出$2本身。
    [theshu@theshu ~]$ sh noeval.sh arg1 arg2
    arg2 #<==输出了$2的值

2.3. exec

  • 命令格式:exec 命令参数
  • 功能:exec命令能够在不创建新的子进程的前提下,转去执行指定的命令,当指定的命令执行完毕后,该进程(也就是最初的Shell)就终止了,示例如下:
    1
    2
    3
    [root@theshu ~]# exec date
    Sun Feb 25 20:24:18 CST 2018
    [theshu@theshu ~]$ #<==退到普通用户模式下了

当使用exec打开文件后,read命令每次都会将文件指针移动到文件的下一行进行读取,知道文件末尾,利用这个可以实现处理文件内容。exec的功能示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[theshu@theshu ~]$ seq 5 > /tmp/tmp.log
[theshu@theshu ~]$ cat exec.sh
exec < /tmp/tmp.log #<==读取log内容
while read line #<==利用read一行行读取处理
do
echo $line #<==打印输出
done
echo ok
[theshu@theshu ~]$ sh exec.sh
1
2
3
4
5
ok

2.4. read

  • 命令格式:read 变量名列表
  • 功能:从标准输入读取字符串等信息,传给Shell程序内部定义的变量。
    此命令将在后文详细讲解。

2.5. shift

  • 命令格式:shift
  • Shift positional parameters
  • 功能:shift语句会按如下方式重新命令所有的位置参数变量,即$2成为$1$3称为$2等,以此类推,在程序中每使用一次shift语句,都会使所有的位置参数一次向左移动一个位置,并使位置参数$#减1,直到减到0为止。
  • shift功能介绍:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    [theshu@theshu ~]$ help shift
    shift: shift [n]
    Shift positional parameters.
    Rename the positional parameters $N+1,$N+2 ... to $1,$2 ... If N is
    not given, it is assumed to be 1.
    Exit Status:
    Returns success unless N is negative or greater than $#.

shift命令的主要作用是将位置参数$1、$2等进行左移,即如果位置参数是$3、$2、$1,那么执行一次shift后,$3就变成了$2、$2变成了$1,$1就消失了。

shift命令的使用示例:

1
2
3
4
5
6
7
8
9
[theshu@theshu ~]$ cat n.sh
echo $1 $2
if [ $# -eq 2 ];then
shift
echo $1
fi
[theshu@theshu ~]$ sh n.sh 1 2
1 2 #<==这是echo $1 $2的结果
2 #<==这里是echo $1的结果,但是输出的是传参时$2的值

应用场景:当我们写Shell希望像命令行的命令通过参数控制不同的功能时,就会先传一个类似-c的参数,然后再接内容。

1
2
3
[theshu@theshu ~]$ sh n.sh -c theshu
-c theshu #<==对应$1 $2的输出
theshu #<==对应$1的输出,因为执行shift,因此第二个参数$2的内容,就变成了$1

2.6. exit

  • 命令格式:exit
  • Exit the shell
  • 功能:退出Shell程序。在exit之后可以有选择地指定一个数位作为返回状态。

3. Shell变量子串知识及实践

3.1. Shell变量子串介绍

Shell变量字串的常用操作见下表。可以执行man bash命令之后,搜索Parameter Expansion找到相应的帮助知识,对于Shell新手来说,次部分内容可以暂时忽略,可以学完全部内容之后再学这一部分即可。

序号 表达式 说明
1 ${parameter} 返回变量$parameter的内容
2 ${井parameter} 返回变量$parameter内容的长度(按字符),也适用于特殊变量
3 ${parameter:offset} 在变量${parameter}中,从位置offset之后开始提取字串到结尾
4 ${parameter:offset:length} 在变量${parameter}中,从位置offset之后开始提取长度为length的字串
5 ${parameter#word} 从变量${parameter}开头开始删除最短匹配的word子串
6 ${parameter##word} 从变量${parameter}开头开始删除最长匹配的word子串
7 ${parameter%word} 从变量${parameter}结尾开始删除最短匹配的word子串
8 ${parameter%%word} 从变量${parameter}结尾开始删除最长匹配的word子串
9 ${parameter/pattern/string} 使用string代替第一个匹配的pattern
10 ${parameter//pattern/string} 使用string代替所有匹配的pattern

3.2. Shell变量子串的实践

  • 准备:定义THESHU变量,赋值内容为”I am theshu”,操作代码如下:

    1
    2
    3
    4
    5
    [theshu@theshu ~]$ THESHU="I am theshu"
    [theshu@theshu ~]$ echo ${THESHU}
    I am theshu
    [theshu@theshu ~]$ echo $THESHU
    I am theshu
  • 例:返回THESHU变量值的长度。通过在变量名前加#,就可以打印变量值的长度:

    1
    2
    [theshu@theshu ~]$ echo ${井THESHU}
    11
  • 例:Shell的其它打印变量长度的方法。

    1
    2
    3
    4
    5
    6
    [theshu@theshu ~]$ echo $THESHU | wc -L
    11 #<==输出变量值,然后通过管道交给wc计算长度
    [theshu@theshu ~]$ expr length "$THESHU"
    11 #<==利用expr的length函数计算变量长度
    [theshu@theshu ~]$ echo "$THESHU" | awk '{print length($0)}'
    11 #<==利用awk的length函数计算变量长度,也可无"($0)"这几个字符

提示:上述计算变量长度的方法中,变量的子串方式是最快的,即${井THESHU}

  • 例:利用time命令及for循环对几种获取字符串长度的方法进行性能比较
  1. 变量自带的获取长度的方法(echo ${井char}

    1
    2
    3
    4
    5
    [theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;echo ${#char} &> /dev/null;done
    real 0m8.472s #<==变量自带的获取长度的方法用时最少,效率最高
    user 0m5.331s
    sys 0m3.099s
  2. 利用管道加wc的方法(echo ${cahr} | wc -L

    1
    2
    3
    4
    5
    [theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;echo ${char} | wc -L &> /dev/null;done
    real 0m27.965s #<==使用了管道加wc -L计算,结果倒数第二,仅次于管道加awk统计的
    user 0m15.910s
    sys 0m11.917s
  3. 利用expr自带的length方法(expr length "${char}"

    1
    2
    3
    4
    5
    [theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;expr length "${cahr}" &> /dev/null;done
    real 0m15.283s #<==好于使用管道和wc的计算方法,但是比变量自带的获取长度的方法要差一些
    user 0m9.217s
    sys 0m5.990s
  4. 利用awk自带的length函数方法

    1
    2
    3
    4
    5
    [theshu@theshu ~]$ time for n in {1..10000};do char=`seq -s "theshu" 100`;echo $char | awk '{print length($0)}' &> /dev/null;done
    real 0m30.363s #<==使用了管道还有awk的函数计算,结果最差
    user 0m16.500s
    sys 0m13.709s

可以看到,这几种方法的速度相差几十到上百倍,一般情况下调用外部命令来处理的方式与使用内置操作的速度相差较大。在Shell编程中,应尽量使用内置操作或函数来完成。

有关获取字符串长度的几种统计方法的性能比较如下:

  • 变量自带的计算长度的方法的效率最高,在要求效率的场景中尽量多用
  • 使用管道统计的方法的效率都比较差,在要求效率的场景中尽量不用
  • 对于日常简单的脚本计算,读者可以根据自己所擅长的或易用的程度去选择

  • 例:截取THESHU变量的内容,从第2个字符之后开始截取,默认截取后面字符的全部,第2个字符不包含在内,也可理解为删除前面的多个字符

    1
    2
    3
    4
    [theshu@theshu ~]$ echo ${THESHU}
    I am theshu
    [theshu@theshu ~]$ echo ${THESHU:2}
    am theshu #<==相当于从I后面的空格开始计算,截取到了结尾
  • 例:截取THESHU变量的内容,从第2个字符之后开始截取,截取2个字符

    1
    2
    3
    4
    5
    [theshu@theshu ~]$ echo ${THESHU:2:2}
    am
    提示:这个功能类似于cut命令-c参数的功能,如下;
    [theshu@theshu ~]$ echo ${THESHU} | cut -c 3-4
    am
  • 例:从变量$THESHU内容的开头开始删除最短匹配"a*C""a*c"的子串

    1
    2
    3
    4
    5
    6
    7
    [theshu@theshu ~]$ THESHU=abcABC123ABCabc
    [theshu@theshu ~]$ echo $THESHU
    abcABC123ABCabc
    [theshu@theshu ~]$ echo ${THESHU#a*C} #<==从开头开始删除最短匹配"a*C"的子串
    123ABCabc #<==从开头开始删除了abcABC
    [theshu@theshu ~]$ echo ${THESHU#a*c}
    ABC123ABCabc #<==从开头开始删除了abc
  • 例:从变量$THESHU开头开始删除最长匹配"a*C""a*c"的子串

    1
    2
    3
    4
    5
    6
    7
    [theshu@theshu ~]$ THESHU=abcABC123ABCabc
    [theshu@theshu ~]$ echo $THESHU
    abcABC123ABCabc
    [theshu@theshu ~]$ echo ${THESHU##a*c} #<==从开头开始删除最长匹配"a*c"的子串
    #<==结果为空了,说明都匹配了,全部都删除了
    [theshu@theshu ~]$ echo ${THESHU##a*C} #<==从开头开始删除最长匹配"a*C"的子串
    abc #<==结果为abc,说明匹配了abcABC123ABC并删除了这些字符
  • 例:从变量$THESHU结尾开始删除最短匹配"a*C""a*c"的子串

    1
    2
    3
    4
    5
    6
    7
    [theshu@theshu ~]$ THESHU=abcABC123ABCabc
    [theshu@theshu ~]$ echo $THESHU
    abcABC123ABCabc
    [theshu@theshu ~]$ echo ${THESHU%a*C} #<==从结尾开始删除最短匹配"a*c"的子串
    abcABC123ABCabc #<==原样输出,因为从结尾开始"a*C"没有匹配上任何子串,因此,没有删除任何字符
    [theshu@theshu ~]$ echo ${THESHU%a*c} #<==从结尾开始删除最短匹配"a*c"的子串
    abcABC123ABC #<==从结尾开始删除最短匹配"a*c",即删除了结尾的abc三个字符
  • 例:从变量$THESHU结尾开始删除最长匹配"a*C""a*c"的子串

    1
    2
    3
    4
    5
    6
    7
    [theshu@theshu ~]$ THESHU=abcABC123ABCabc
    [theshu@theshu ~]$ echo $THESHU
    abcABC123ABCabc
    [theshu@theshu ~]$ echo ${THESHU%%a*C} #<==从结尾开始删除最长匹配"a*C"的子串
    abcABC123ABCabc #<==原样输出,因为从结尾开始"a*C"没有匹配上任何子串,因此,没有删除任何字符
    [theshu@theshu ~]$ echo ${THESHU%%a*c} #<==从结尾开始删除最长匹配"a*c"的子串
    #<==从结尾开始删除最长匹配"a*C"的字符串,即删除全部字符

有关上述匹配删除的小结:

  • #表示从开头删除匹配最短
  • ##表示从开头删除匹配最长
  • %表示从结尾删除匹配最短
  • %%表示从结尾删除匹配最长
  • a*C表示匹配的字符串,*表示匹配所有,a*C匹配开头为a、中间为任意多个字符、结尾为C的字符串
  • a*c表示匹配的字符串,*表示匹配所有,a*c匹配开头为a、中间为任意多个字符、结尾为c的字符串

  • 例:使用thewang字符代替变量$THESHU匹配的theshu字符串

    1
    2
    3
    4
    5
    6
    7
    [theshu@theshu ~]$ THESHU="I am theshu,yes,theshu"
    [theshu@theshu ~]$ echo $THESHU
    I am theshu,yes,theshu
    [theshu@theshu ~]$ echo ${THESHU/theshu/thewang} #<==替换匹配的第一个字符串
    I am thewang,yes,theshu
    [theshu@theshu ~]$ echo ${THESHU//theshu/thewang} #<==替换匹配的所有字符串
    I am thewang,yes,thewang

有关替换的小结:

  • 一个/表示替换匹配的第一个字符串
  • 两个///表示替换匹配的所有字符串

3.3. 变量子串的生产场景应用案例

可以用来批量修改文件名:假如本目录里有一些文件,这些文件的名字当中都有theshu这个字符串,批量修改去掉它:

1
# for f in `ls *theshu*`;do mv $f `echo ${f//theshu/}`;done

4. Shell特殊扩展变量的知识与实践

4.1. Shell特殊扩展变量介绍

Shell的特殊扩展变量说明见下表,也可以执行man bash命令,然后搜索Parameter Expansion查找相关的帮助内容。

表达式 说明
${parameter:-word} 如果parameter的变量值为空或未赋值,则会返回word字符串并替代变量的值。用途:如果变量未定义,则返回备用的值,防止变量为空值或因未定义而导致异常
${parameter:=word} 如果parameter的变量值为空或未赋值,则设置这个变量值为word,并返回其值。位置变量和特殊变量不适用。用途:基本上同一个${parameter:-word},但该变量又额外给parameter变量赋值了
${parameter:?word} 如果parameter变量值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值。用途:用于捕捉由于变量未定义而导致的错误,并退出程序
${parameter:+word} 如果parameter变量值为空或未赋值,则什么都不做,否则word字符串将替代变量的值

在上表中,每个表达式内的冒号都是可选的。如果省略了表达式中的冒号,则将每个定义中的为空或未赋值部分改为未赋值,也就是说,运算符仅用于测试变量是否未赋值。

4.2. Shell特殊扩展变量的实践

4.2.1. ${parameter:-word}功能实践

${parameter:-word}的作用是如果parameter变量值为空或未赋值,则会返回word字符串替代变量的值。

示例1:

1
2
3
4
5
6
7
[root@theshu ~]# echo $test
#<==变量未设置,所以输出时为空
[root@theshu ~]# result=${test:-UNSET} #<==若test没值,则返回UNSET
[root@theshu ~]# echo $result #<==打印result变量,返回UNSET,因为test没有赋值
UNSET
[root@theshu ~]# echo ${test}
#<==注意,此时打印test变量还是为空

结论:对于${test:-UNSET},当test变量没有值时,就返回变量结尾设置的UNSET字符串。

示例2:

1
2
3
4
5
6
7
8
9
10
[root@theshu ~]# test=theshu #<==给test变量赋值theshu字符串
[root@theshu ~]# echo $test
theshu
[root@theshu ~]# result=${test:-UNSET} #<==重新定义result
[root@theshu ~]# echo $result
theshu #<==因为test已赋值,因此,打印result就输出了test的值theshu,而不是原来的UNSET
[root@theshu ~]# result=${test-UNSET}
#<==提示:这个变量的功能可以用来判断变量是否已定义
[root@theshu ~]# echo $result #<==定义时忽略了冒号
theshu

结论:当test有值时,就打印result变量,返回test变量的内容。

4.2.2. ${parameter:=word}功能实践

${parameter:=word}的作用是:如果parameter变量值为空或未赋值,就设置这个变量值为word,并返回其值。位置变量和特殊变量不适用。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@theshu ~]# unset result #<==撤销result变量定义
[root@theshu ~]# echo $result
[root@theshu ~]# unset test #<==撤销test变量定义
[root@theshu ~]# echo $test
[root@theshu ~]# result=${test:=UNSET} #<重新对变量result进行定义
[root@theshu ~]# echo $result
UNSET
[root@theshu ~]# echo $test
UNSET #<==注意,这里的test原来是没有定义的,现在已经被赋值UNSET了,这是和":-"表达式的区别
[root@theshu ~]# result=${test=UNSET} #<==定义时忽略了冒号
[root@theshu ~]# echo $result
UNSET #<==打印结果和带冒号时没有变化
[root@theshu ~]# echo $test
UNSET #<==打印结果和带冒号时没有变化

当变量result值里的变量test值没有定义时,会给变量result赋值:=后面的内容,同时会把:=后面的内容赋值给变量result值里没有定义的变量test

这个变量的功能可以解决变量没有定义的问题,并确保没有定义的变量始终有值。

4.2.3. ${parameter:?word}功能实践

${parameter:?word}的作用是:如果parameter变量值为空或未赋值,那么word字符串将被作为标准错误输出,否则输出变量的值。

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@theshu ~]# echo ${key:?not defined}
-bash: key: not defined #<==错误提示,只不过是事先定义好的错误输出
#<==key变量没有定义,因此,把"not defined"最为标准错误输出
[root@theshu ~]# echo ${key?not defined} #<==去掉冒号定义,并输出,结果一致
-bash: key: not defined
[root@theshu ~]# key=1 #<==给变量赋值1
[root@theshu ~]# echo ${key:?not defined}
1 #<==因为key有值了,所以,打印key的值1
[root@theshu ~]# echo ${key?not defined}
1 #<==去掉冒号定义,并输出,结果一致
[root@theshu ~]# unset key #<==取消key的定义
[root@theshu ~]# echo ${key:?not defined} #<==由打印错误提示了
-bash: key: not defined

本例的用法可以用于设定由于变量未定义而报错的具体内容,如:”not defined”。

4.2.4. ${parameter:+word}功能实践

${parameter:+word}的作用是:如果parameter变量值为空或未赋值,则什么都不做,否则word字符串将替代变量的值。

范例:

1
2
3
4
5
6
7
[root@theshu ~]# theshu=${wang:+word} #<==wang变量未定义
[root@theshu ~]# echo $theshu #<==因为wang变量未定义,所以打印theshu变量值为空
[root@theshu ~]# wang=20 #<==wang变量赋值为20
[root@theshu ~]# theshu=${wang:+word} #<==注意,这里一定要重新定义theshu
[root@theshu ~]# echo $theshu
word #<==因为wang变量有值,所以打印theshu变量输出为`:+`后面的内容

此功能可用于测试变量(wang的位置)是否存在,如果theshu的值为word,则证明wang变量有值。

4.3. Shell特殊扩展变量的生产场景应用案例

一个生产案例:删除7天前的过期的数据备份:

  • 如果忘记了定义path变量,又不希望path值为空值,就可以定义/tmp替代path空值的返回值,如下:

    1
    2
    3
    4
    5
    6
    [root@theshu ~]# cat del.sh
    find ${path-/tmp} -name "*.tar.gz" -type f -mtime +7 | xargs rm -f
    [root@theshu ~]# sh -x del.sh
    + find /tmp -name '*.tar.gz' -type f -mtime +7
    + xargs rm -f
    #<==执行时,系统会自动删除/tmp下的文件
  • 如果忘了定义path变量,并且还未做特殊变量定义,那么命令就会出现意外,如下:

    1
    2
    3
    4
    5
    [root@theshu ~]# cat a.sh
    find ${path} -name "*.tar.gz" -type f -mtime +7 | xargs rm -f
    [root@theshu ~]# sh -x a.sh
    + find -name '*.tar.gz' -type f -mtime +7 #<==这条命令明显没有指定路径,因此将会导致异常
    + xargs rm -f

OK

0%